package ch.ethz.fcl.metrobuzz.render.techniques.circos;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.render.techniques.bezier.Bezier;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorTable;
import ch.ethz.fcl.metrobuzz.render.techniques.projection.Projection;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.tools.MBGL;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

/**
 * ribbons indicate interchange from one link to another
 * 
 * @author Zeng Wei
 * @email zeng@arch.ethz.ch
 * 
 */
public class Ribbon {
	private static int curvePointsSize = 100;

	// render ribbons with color change
	public static void renderAsColorChange(Projection proj, int colorIndex1,
			int colorIndex2, Point2D[] ctrlPoints1, Point2D[] ctrlPoints2) {
		Point2D[] curve1, curve2;

		curve1 = Bezier.getBezierSpline(curvePointsSize, ctrlPoints1);
		curve2 = Bezier.getBezierSpline(curvePointsSize, ctrlPoints2);

		GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
		for (int k = 0; k <= curvePointsSize; k++) {
			MBColorTable.assignGradualColor(colorIndex1, colorIndex2, 1.0f * k
					/ curvePointsSize, 1.0f, 2);
			GL11.glVertex3f(curve1[k].getX(), curve1[k].getY(), 0);
			GL11.glVertex3f(curve2[k].getX(), curve2[k].getY(), 0);
		}
		GL11.glEnd();

		boolean blackOutline = true;
		// only display ribbon with gap larger than 4 pixels as black
		if (proj.getScreenDistance(curve1[curvePointsSize / 2],
				curve2[curvePointsSize / 2]) < 4) {
			blackOutline = false;
		}
		GL11.glLineWidth(1.0f);
		GL11.glBegin(GL11.GL_LINE_STRIP);
		for (int k = 0; k <= curvePointsSize; k++) {
			if (blackOutline)
				MBColorTable.assignColor(MBColorTable.BLACK, 0.5f);
			else
				MBColorTable.assignGradualColor(colorIndex1, colorIndex2, 1.0f
						* k / curvePointsSize, 1.0f, 2);
			GL11.glVertex3f(curve1[k].getX(), curve1[k].getY(), 0);
		}
		GL11.glEnd();

		GL11.glBegin(GL11.GL_LINE_STRIP);
		for (int k = 0; k <= curvePointsSize; k++) {
			if (blackOutline)
				MBColorTable.assignColor(MBColorTable.BLACK, 0.5f);
			else
				MBColorTable.assignGradualColor(colorIndex1, colorIndex2, 1.0f
						* k / curvePointsSize, 1.0f, 2);
			GL11.glVertex3f(curve2[k].getX(), curve2[k].getY(), 0);
		}
		GL11.glEnd();
	}

	// render ribbons using glyph
	public static void renderAsGlyph(Projection proj, Point2D[] ctrlPoints1,
			Point2D[] ctrlPoints2, Point2D[] ctrlPointsMid, float[] color1,
			float[] color2) {
		Point2D[] curve1, curve2, curveMid;

		curve1 = Bezier.getBezierSpline(curvePointsSize, ctrlPoints1);
		curve2 = Bezier.getBezierSpline(curvePointsSize, ctrlPoints2);
		curveMid = Bezier.getBezierSpline(curvePointsSize, ctrlPointsMid);

		renderGlyph(proj, curve1, curve2, curveMid, color1, color2);
	}

	// render ribbons using glyph with information of mid spline control points
	// and the starting and ending width, only support middle control points
	// size 3
	public static void renderAsGlyph(Projection proj, Point2D[] ctrlPointsMid,
			float startWidth, float endWidth, float[] color1, float[] color2) {
		if (ctrlPointsMid.length != 3)
			return;
		Point2D p0 = ctrlPointsMid[0], p1 = ctrlPointsMid[1], p2 = ctrlPointsMid[2];
		Point2D[] upCtrlPts = new Point2D[3];
		Point2D[] downCtrlPts = new Point2D[3];

		Vector2d p01Dir = new Vector2d(p1.getX() - p0.getX(), p1.getY()
				- p0.getY());
		Vector2d upP01BoundDir = MBTools.rotate(p01Dir, Math.PI / 2);
		upCtrlPts[0] = p0.getPointAt(upP01BoundDir, startWidth / 2);
		Vector2d downP01BoundDir = MBTools.rotate(p01Dir, -Math.PI / 2);
		downCtrlPts[0] = p0.getPointAt(downP01BoundDir, startWidth / 2);

		Vector2d p02Dir = new Vector2d(p2.getX() - p0.getX(), p2.getY()
				- p0.getY());
		Vector2d upP02BoundDir = MBTools.rotate(p02Dir, Math.PI / 2);
		upCtrlPts[1] = p1
				.getPointAt(upP02BoundDir, (startWidth + endWidth) / 4);
		Vector2d downP02BoundDir = MBTools.rotate(p02Dir, -Math.PI / 2);
		downCtrlPts[1] = p1.getPointAt(downP02BoundDir,
				(startWidth + endWidth) / 4);

		Vector2d p12Dir = new Vector2d(p2.getX() - p1.getX(), p2.getY()
				- p1.getY());
		Vector2d upP12BoundDir = MBTools.rotate(p12Dir, Math.PI / 2);
		upCtrlPts[2] = p2.getPointAt(upP12BoundDir, endWidth / 2);
		Vector2d downP12BoundDir = MBTools.rotate(p12Dir, -Math.PI / 2);
		downCtrlPts[2] = p2.getPointAt(downP12BoundDir, endWidth / 2);

		renderAsGlyph(proj, upCtrlPts, downCtrlPts, ctrlPointsMid, color1,
				color2);
	}

	public static void renderGlyph(Projection proj, Point2D[] spline1,
			Point2D[] spline2, Point2D[] spline_mid, float[] color1,
			float[] color2) {
		int k = 0;
		int splinePointsSize = spline1.length;
		while (k < splinePointsSize) {
			Point2D p1 = new Point2D(spline1[k].getX(), spline1[k].getY());
			Point2D p2 = new Point2D(spline2[k].getX(), spline2[k].getY());

			float width = p1.distance(p2);
			int nextIndex = nextPointIndex(spline2, width, k);

			if (k == nextIndex) {
				break;
			}

			if (nextIndex == splinePointsSize - 1) {
				float dist = p1.distance(spline1[nextIndex]);
				if (dist < width * 0.5f)
					break;
			}
			int midIndex = (k + nextIndex) / 2;

			Point2D p3 = new Point2D(spline2[nextIndex].getX(),
					spline2[nextIndex].getY());
			Point2D p4 = new Point2D(spline1[nextIndex].getX(),
					spline1[nextIndex].getY());

			if (proj.getScreenDistance(p1, p2) < 4) {
				Point2D[] shape = { p1, p4, p3, p2 };
				if (color1 == null || color2 == null) {
					MBColorTable.assignColor(MBColorTable.LIGHTGREY, 1.0f);
					MBGL.render(shape, GL11.GL_POLYGON);
				} else {
					float[] p12Color = MBTools.map(k, 0, splinePointsSize,
							color1, color2);
					float[] p34Color = MBTools.map(nextIndex, 0,
							splinePointsSize, color1, color2);
					GL11.glBegin(GL11.GL_POLYGON);
					MBGL.color(p12Color);
					MBGL.render(p1);
					MBGL.render(p2);

					MBGL.color(p34Color);
					MBGL.render(p3);
					MBGL.render(p4);
					GL11.glEnd();
				}

				GL11.glLineWidth(1.0f);
				MBColorTable.assignColor(MBColorTable.BLACK, 0.5f);
				MBGL.render(shape, GL11.GL_LINES);
			} else {
				GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
				for (int i = k; i <= midIndex; i++) {
					if (color1 == null || color2 == null)
						MBColorTable.assignColor(MBColorTable.LIGHTGREY, 1.0f);
					else {
						float[] color = MBTools.map(k, 0, splinePointsSize,
								color1, color2);
						MBGL.color(color);
					}
					MBGL.render(spline1[i]);
					MBGL.render(spline_mid[i - k + midIndex]);
				}
				MBGL.render(spline_mid[nextIndex]);
				GL11.glEnd();

				GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
				for (int i = k; i <= midIndex; i++) {
					if (color1 == null || color2 == null)
						MBColorTable.assignColor(MBColorTable.LIGHTGREY, 1.0f);
					else {
						float[] color = MBTools.map(k, 0, splinePointsSize,
								color1, color2);
						MBGL.color(color);
					}
					MBGL.render(spline2[i]);
					MBGL.render(spline_mid[i - k + midIndex]);
				}
				MBGL.render(spline_mid[nextIndex]);
				GL11.glEnd();

				GL11.glLineWidth(1.0f);
				MBColorTable.assignColor(MBColorTable.BLACK, 0.5f);
				GL11.glBegin(GL11.GL_LINE_STRIP);

				for (int i = k; i <= midIndex; i++)
					MBGL.render(spline1[i]);
				MBGL.render(spline_mid[nextIndex]);
				for (int i = midIndex; i >= k; i--)
					MBGL.render(spline2[i]);
				MBGL.render(spline_mid[midIndex]);
				MBGL.render(spline1[k]);
				GL11.glEnd();
			}

			k = nextIndex;
		}
	}

	private static int nextPointIndex(Point2D[] points, float dist, int curIndex) {
		float length = 0;
		int index = curIndex;
		Point2D p = points[curIndex];
		while (length < dist && index < points.length - 1) {
			Point2D nextP = points[index];
			length += p.distance(nextP);
			p = nextP;
			index++;
		}

		return index;
	}
}
